package com.gitee.wsl.struct.command.model.parameters.groups

import com.gitee.wsl.struct.command.model.command.BaseCliktCommand
import com.gitee.wsl.struct.command.context.Context
import com.gitee.wsl.struct.command.model.ParameterHolder
import com.gitee.wsl.struct.command.internal.finalizeOptions
import com.gitee.wsl.struct.command.model.parameters.GroupableOption
import com.gitee.wsl.struct.command.model.parameters.options.Option
import com.gitee.wsl.struct.command.parsers.OptionInvocation
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

/**
 * A group of options that can be shown together in help output, or restricted to be [cooccurring].
 *
 * Declare a subclass with option delegate properties, then use an instance of your subclass is a
 * delegate property in your command with [provideDelegate].
 *
 * ### Example:
 *
 * ```
 * class UserOptions : OptionGroup(name = "User Options", help = "Options controlling the user") {
 *   val name by option()
 *   val age by option().int()
 * }
 *
 * class Tool : CliktCommand() {
 *   val userOptions by UserOptions()
 * }
 * ```
 */
open class OptionGroup(
    name: String? = null,
    help: String? = null,
) : ParameterGroup, ParameterHolder {
    internal val options: MutableList<GroupableOption> = mutableListOf()
    override val groupName: String? = name
    override val groupHelp: String? = help

    override fun registerOption(option: GroupableOption) {
        option.parameterGroup = this
        options += option
    }

    override fun finalize(context: Context, invocationsByOption: Map<Option, List<OptionInvocation>>) {
        finalizeOptions(context, options, invocationsByOption)
    }

    override fun postValidate(context: Context) = options.forEach { it.postValidate(context) }
}

operator fun <T : OptionGroup> T.provideDelegate(
    thisRef: BaseCliktCommand<*>,
    prop: KProperty<*>,
): ReadOnlyProperty<BaseCliktCommand<*>, T> {
    thisRef.registerOptionGroup(this)
    options.forEach { thisRef.registerOption(it) }
    return ReadOnlyProperty { _, _ -> this@provideDelegate }
}